#include "EffectTemplatePCH.h"
#include "EffectManager.h"
#include "Effect.h"
#include "EffectParameter.h"

EffectParameter EffectParameter::InvalidEffectParameter(NULL, NULL );

EffectParameter::EffectParameter( CGcontext context, CGparameter parameter )
: m_cgContext( context )
, m_cgParameter( parameter )
, m_cgType( CG_UNKNOWN_TYPE )
, m_cgBaseType( CG_UNKNOWN_TYPE )
, m_cgVariability( CG_UNKNOWN )
, m_iNumRows( 0 )
, m_iNumCols( 0 )
, m_bIsValid( false )
, m_bIsDirty( false )
, m_bIsDefault( true )
{
    if ( m_cgParameter != NULL && cgIsParameter(m_cgParameter) )
    {
        m_bIsValid = true;

        const char* name = cgGetParameterName(m_cgParameter);
        if ( name != NULL )
        {
            m_ParameterName = name;
        }
        const char* semantic = cgGetParameterSemantic(m_cgParameter);
        if ( semantic != NULL )
        {
            m_ParameterSemantic = semantic;
        }

        m_cgParameterClass = cgGetParameterClass(m_cgParameter);
        m_cgType = cgGetParameterType(m_cgParameter);
        m_cgBaseType = cgGetParameterBaseType( m_cgParameter );
        m_cgVariability = cgGetParameterVariability(m_cgParameter);

        GetInternalValue();
    }
    else
    {
        m_ParameterName = "InvalidEffectParameter";
    }
}

EffectParameter::~EffectParameter()
{
    Destroy();
}

void EffectParameter::Destroy()
{
    // Delete any nested parameters
    ParameterList::iterator iter = m_NestedParameters.begin();
    while ( iter != m_NestedParameters.end() )
    {
        delete (*iter);
        ++iter;
    }
    m_NestedParameters.clear();
    m_NestedParametersByName.clear();

    switch ( m_cgParameterClass )
    {
    case CG_PARAMETERCLASS_SCALAR:
    case CG_PARAMETERCLASS_VECTOR:
    case CG_PARAMETERCLASS_MATRIX:
        {
            switch ( m_cgBaseType )
            {
            case CG_BOOL:
                {
                    int* values = boost::any_cast<int*>(m_ParameterValue);
                    delete [] values;
                }
                break;
            case CG_INT:
                {
                    int* values = boost::any_cast<int*>(m_ParameterValue);
                    delete [] values;
                }
                break;
            case CG_FLOAT:
                {
                    float* values = boost::any_cast<float*>(m_ParameterValue);
                    delete [] values;
                }
                break;
            case CG_DOUBLE:
                {
                    double* values = boost::any_cast<double*>(m_ParameterValue);
                    delete [] values;
                }
                break;
            }
        }
        break;
    }
}

const std::string& EffectParameter::GetParameterName() const
{
    return m_ParameterName;
}

const std::string& EffectParameter::GetParameterSemantic() const
{
    return m_ParameterSemantic;
}

bool EffectParameter::HasSemantic() const
{
    // A parameter has a semantic if the semantic string is not empty.
    return ( !m_ParameterSemantic.empty() );
}

bool EffectParameter::IsValid() const
{
    return m_bIsValid;
}

bool EffectParameter::IsDefault() const
{
    return m_bIsDefault;
}

void EffectParameter::SetVariability( ParameterVariability variability )
{
    if ( m_bIsValid )
    {
        cgSetParameterVariability( m_cgParameter, (CGenum)variability );
        CGenum variablity = cgGetParameterVariability(m_cgParameter);
        if ( variablity != m_cgVariability )
        {
            m_cgVariability = variablity;
            m_bIsDirty = true;
        }
    }
}

void EffectParameter::SetValue( const boost::any& value )
{
    // Don't try to set the values of non valid parameters
    if ( !m_bIsValid ) return;

    // Arrays, structs and samplers can't be set directly.
    // You need to get the nested parameter in order to set them.
    switch ( m_cgParameterClass )
    {
    case CG_PARAMETERCLASS_SCALAR:
    case CG_PARAMETERCLASS_VECTOR:
    case CG_PARAMETERCLASS_MATRIX:
        {
            size_t size = m_iNumRows * m_iNumCols;
            switch ( m_cgBaseType )
            {
            case CG_BOOL:
                {
                    const bool* pValue = boost::any_cast<const bool*>( value );
                    SetBoolValue( pValue, size );
                }
                break;
            case CG_INT:
                {
                    const int* pValue = boost::any_cast<const int*>( value );
                    SetIntValue( pValue, size );
                }
                break;
            case CG_FLOAT:
                {
                    const float* pValue = boost::any_cast<const float*>( value );
                    SetFloatValue( pValue, size );
                }
                break;
            case CG_DOUBLE:
                {
                    const double* pValue = boost::any_cast<const double*>( value );
                    SetDoubleValue( pValue, size );
                }
                break;
            default: 
                {
                    std::cerr << "EffectParameter::SetValue: Failed to set value on " << cgGetTypeString(m_cgBaseType) << " type parameter." << std::endl;
                }
                break;
            }
        }
        break;
    case CG_PARAMETERCLASS_SAMPLER:
        {
            SetSampler( value );
        }
        break;
    case CG_PARAMETERCLASS_OBJECT:
        {
            SetObject( value );
        }
        break;
    default: 
        {
            std::cerr << "EffectParameter::SetValue: Failed to set value on " << cgGetParameterClassString(m_cgParameterClass) << " class parameter." << std::endl;
        }
        break;
    }
}

void EffectParameter::SetBoolValue( const bool* value, size_t size )
{
    assert( m_cgBaseType == CG_BOOL && ( m_iNumRows * m_iNumCols ) == size );
    int* pInternalValue = boost::any_cast<int*>( m_ParameterValue );
    for ( unsigned int i = 0; i < size; ++i )
    {   // Our bool types are stored as ints.
        // so we need to cast to the correct type.
        pInternalValue[i] = (int)value[i];
    }
    m_bIsDirty = true;
}

void EffectParameter::SetIntValue( const int* value, size_t size )
{
    assert( m_cgBaseType == CG_INT && ( m_iNumRows * m_iNumCols ) == size );
    int* pInternalValue = boost::any_cast<int*>( m_ParameterValue );
    memcpy( pInternalValue, value, sizeof(int) * size );
    m_bIsDirty = true;
}

void EffectParameter::SetFloatValue( const float* value, size_t size )
{
    assert( m_cgBaseType == CG_FLOAT && ( m_iNumRows * m_iNumCols ) == size );
    float* pInternalValue = boost::any_cast<float*>( m_ParameterValue );
    memcpy( pInternalValue, value, sizeof(float) * size );
    m_bIsDirty = true;
}

void EffectParameter::SetDoubleValue( const double* value, size_t size )
{
    assert( m_cgBaseType == CG_DOUBLE && ( m_iNumRows * m_iNumCols ) == size );
    double* pInternalValue = boost::any_cast<double*>( m_ParameterValue );
    memcpy( pInternalValue, value, sizeof(double) * size );
}

void EffectParameter::SetSampler( const boost::any& value )
{
    // Try to cast to a GLuint
    GLuint textureID = boost::any_cast<GLuint>(value);
    m_ParameterValue = textureID;
    m_bIsDirty = true;
}

void EffectParameter::SetObject( const boost::any& value )
{
    switch ( m_cgType )
    {
    case CG_TEXTURE:
        {
            // Try to cast to a GLuint
            GLuint textureID = boost::any_cast<GLuint>(value);
            m_ParameterValue = textureID;
        }
        break;
    case CG_STRING:
        {
            // Try to cast to a string type
            try 
            {
                std::string strValue = boost::any_cast<std::string>( value );
                m_ParameterValue = strValue;
            }
            catch ( boost::bad_any_cast& )
            {
                // Try to cast to a char array and copy the string
                const char* pStr = boost::any_cast<const char*>( value );
                m_ParameterValue = std::string( pStr );
            }
        }
        break;
    default: 
        {
            std::cerr << "EffectParameter::SetValue: Failed to set value on " << cgGetTypeString(m_cgBaseType) << " type parameter." << std::endl;
        }
        break;
    }

    m_bIsDirty = true;
}


void EffectParameter::GetInternalValue()
{
    switch ( m_cgParameterClass )
    {
    case CG_PARAMETERCLASS_SCALAR:
    case CG_PARAMETERCLASS_VECTOR:
    case CG_PARAMETERCLASS_MATRIX:
        {
            m_iNumRows = cgGetParameterRows( m_cgParameter );
            m_iNumCols = cgGetParameterColumns( m_cgParameter );

            int iSize = ( m_iNumRows * m_iNumCols );
            assert( iSize > 0 );

            switch ( m_cgBaseType )
            {
            case CG_BOOL:
                {
                    // teat bool as int?
                    int* values = new int[iSize];
                    cgGetParameterValueic( m_cgParameter, iSize, values );
                    m_ParameterValue = values;
                }
                break;
            case CG_INT:
                {
                    int* values = new int[iSize];
                    cgGetParameterValueic( m_cgParameter, iSize, values );
                    m_ParameterValue = values;
                }
                break;
            case CG_FLOAT:
                {
                    float* values = new float[iSize];
                    cgGetParameterValuefc(m_cgParameter, iSize, values );
                    m_ParameterValue = values;
                }
                break;
            case CG_DOUBLE:
                {
                    double* values = new double[iSize];
                    cgGetParameterValuedc( m_cgParameter, iSize, values );
                    m_ParameterValue = values;
                }
                break;
            default:
                {
                    std::cerr << "EffectParameter::GetInternalValue: Unsupported parameter base type: " << cgGetTypeString(m_cgBaseType) << std::endl;
                }
                break;
            }
        }
        break;
    case CG_PARAMETERCLASS_STRUCT:
        {
            // Iterate the struct members, setting a named parameter for each one.
            CGparameter parameter = cgGetFirstStructParameter( m_cgParameter );
            while ( parameter != NULL )
            {
                // Create a new parameter and add it to the internal parameter list
                EffectParameter* pParameter = new EffectParameter( m_cgContext, parameter );
                m_NestedParameters.push_back( pParameter );
                m_NestedParametersByName.insert( ParameterMap::value_type( pParameter->GetParameterName(), pParameter ) );
                
                parameter = cgGetNextParameter(parameter);
            }
        }
        break;
    case CG_PARAMETERCLASS_ARRAY:
        {
            // Query the size of the array
            int arraySize = cgGetArraySize( m_cgParameter, 0 );
            for ( int i = 0; i < arraySize; ++i )
            {
                CGparameter parameter = cgGetArrayParameter( m_cgParameter, i );
                EffectParameter* pParameter = new EffectParameter( m_cgContext, parameter );
                m_NestedParameters.push_back( pParameter );
                m_NestedParametersByName.insert( ParameterMap::value_type( pParameter->GetParameterName(), pParameter) );
            }
        }
        break;
    case CG_PARAMETERCLASS_SAMPLER:
        {
            // Iterate over all the state assignments
            CGstateassignment stateAssignment = cgGetFirstSamplerStateAssignment(m_cgParameter);
            while ( stateAssignment != NULL )
            {
                // TODO: Figure out how to bind the state assignments properly...
                stateAssignment = cgGetNextStateAssignment(stateAssignment);
            }
        }
        break;
    case CG_PARAMETERCLASS_OBJECT:
        {
            switch ( m_cgType )
            {
            case CG_STRING:
                {
                    const char* str = cgGetStringParameterValue( m_cgParameter );
                    if ( str != NULL )
                    {
                        m_ParameterValue = std::string( str );
                    }
                }
                break;
            case CG_TEXTURE:
                {
                    // TODO: Parse the annotations if a default texture is specified 
                    // and load that texture based on the value of the annotation?
                    m_ParameterValue = (GLuint)0;
                }
                break;
            }
        }
        break;
    default:
        {
            std::cerr << "EffectParameter::GetInternalValue: Invalid parameter class!" << std::endl;
        }
        break;
    }
}

bool EffectParameter::NeedsUpdate() const
{
    if ( !m_bIsValid ) return false;

    bool bNeedsUpdate = m_bIsDirty;

    // Check all nested parameters
    ParameterList::const_iterator iter = m_NestedParameters.begin();
    while ( !bNeedsUpdate && iter != m_NestedParameters.end() )
    {
        bNeedsUpdate |= (*iter)->NeedsUpdate();
        ++iter;
    }
    
    return bNeedsUpdate;
}

bool EffectParameter::UpdateParameter()
{
    // Don't try to update invalid parameters
    if ( !m_bIsValid ) return false;

    bool bSuccess = true;
    
    // Since this function is called recursively,
    // we can use a static int to determine if the 
    // recursive function has completely unlooped.
    // Of course, this theory breaks if anybody throws exceptions!
    static int siFunctionDepth = 0;

    // If any error occurs after this point, we can use the cgGetFirstError
    // function again to see if an error occurred when trying to set a parameter.
    if ( siFunctionDepth++ == 0 )
    {
        cgGetFirstError();
    }

    try 
    {
        switch ( m_cgParameterClass )
        {
        case CG_PARAMETERCLASS_SCALAR:
        case CG_PARAMETERCLASS_VECTOR:
        case CG_PARAMETERCLASS_MATRIX:
            {
                int numElements = m_iNumRows * m_iNumCols;
                switch ( m_cgBaseType )
                {
                case CG_BOOL:
                    {
                        const int* values = boost::any_cast<int*>( m_ParameterValue );
                        cgSetParameterValueic( m_cgParameter, numElements, values );
                    }
                    break;
                case CG_INT:
                    {
                        const int* values = boost::any_cast<int*>( m_ParameterValue );
                        cgSetParameterValueic( m_cgParameter, numElements, values );                    
                    }
                    break;
                case CG_FLOAT:
                    {
                        const float* values = boost::any_cast<float*>( m_ParameterValue );
                        cgSetParameterValuefc( m_cgParameter, numElements, values );                    
                    }
                    break;
                case CG_DOUBLE:
                    {
                        const double* values = boost::any_cast<double*>( m_ParameterValue );
                        cgSetParameterValuedc( m_cgParameter, numElements, values );                    
                    }
                    break;
                }
            }
            break;
        case CG_PARAMETERCLASS_STRUCT:
        case CG_PARAMETERCLASS_ARRAY:
            {
                // Try to update all the nested parameters
                ParameterList::iterator iter = m_NestedParameters.begin();
                while ( iter != m_NestedParameters.end() )
                {
                    bSuccess |= (*iter)->UpdateParameter();
                    ++iter;
                }
            }
            break;
        case CG_PARAMETERCLASS_SAMPLER:
            {
                // TODO: Do we also need to bind the texture here?
                // Or should the sampler only represent a texture object?
                // NOTE: The texture object needs to be bound before the sampler
                // state is updated.
//                cgSetSamplerState( m_cgParameter );

                GLuint texID = boost::any_cast<GLuint>( m_ParameterValue );
                cgGLSetTextureParameter( m_cgParameter, texID );

            }
            break;
        case CG_PARAMETERCLASS_OBJECT:
            {
                switch ( m_cgBaseType )
                {
                case CG_STRING:
                    {
                        const std::string value = boost::any_cast<std::string>( m_ParameterValue );
                        cgSetStringParameterValue( m_cgParameter, value.c_str() );
                    }
                    break;
                case CG_TEXTURE:
                    {
                        GLuint texID = boost::any_cast<GLuint>( m_ParameterValue );
                        cgGLSetTextureParameter( m_cgParameter, texID );
                    }
                    break;
                }
            }
            break;
        } // switch (m_cgParameterClass)

        // Changes have been applied. Parameter is no longer dirty.
        m_bIsDirty = false;
        m_bIsDefault = false;

    } // try 
    catch ( boost::bad_any_cast& )
    {
        std::cerr << "EffectParameter::UpdateParameter: Failed to set parameter \"" << m_ParameterName << "\" of type " << cgGetTypeString(m_cgType) << std::endl;
    }

    if ( --siFunctionDepth == 0 )
    {
        bSuccess = ( cgGetFirstError() == CG_NO_ERROR );
    }

    return bSuccess;
}

bool EffectParameter::NeedsRecompile() const
{
    bool bNeedsRecompile = false;
    // Check all the nested parameters if any of them require a recompile
    ParameterList::const_iterator iter = m_NestedParameters.begin();
    while ( iter != m_NestedParameters.end() )
    {
        bNeedsRecompile = (*iter)->NeedsRecompile();
        if ( bNeedsRecompile ) break;

        ++iter;
    }

    return ( m_cgVariability == CG_CONSTANT && m_bIsDirty ) || bNeedsRecompile;
}

